package com.moss.cloud.gateway.filter;

import com.moss.cloud.auth.api.service.IGeneralSecurityService;
import com.moss.cloud.auth.api.utils.JwtAccessToken;
import com.moss.cloud.common.core.custom.CustomHttpHeaders;
import com.moss.cloud.common.core.utils.WebFluxUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * 请求url权限校验全局过滤器拦截
 * Gateway 网关基于spring webflux 使用默认的NETTY作为服务器，
 * 非阻塞NIO模式，性能更比zuul1.x高出很多倍，
 * gateway网关也是响应式编程的最好示例
 *
 * @author 瑾年
 * @data 2023年3月7日
 */
@Configuration
@ComponentScan(basePackages = "com.moss.cloud.auth.api")
@Slf4j
public class AccessGatewayFilter implements GlobalFilter, Ordered {

    private static final String BEARER = "Bearer";

    public static final String IGNORE = "/oauth/token";

    private final IGeneralSecurityService authService;

    public AccessGatewayFilter(IGeneralSecurityService authService) {
        this.authService = authService;
    }

    /**
     * 1.首先网关检查token是否有效，无效直接返回401，不调用签权服务
     * 2.调用签权服务器看是否对该请求有权限，有权限进入下一个filter，没有权限返回401
     *
     * @param exchange
     * @param chain
     * @return Mono<Void>
     * @author 瑾年
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        // 网关只信任AUTHORIZATION，下游服务只信任X_CLIENT_TOKEN_USER
        String authentication = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
        HttpHeaders headers = request.getHeaders();
        String method = request.getMethodValue();
        String url = request.getPath().value();
        //不需要网关签权的url
        if (authService.ignoreAuthentication(url)) {
            // 这里重新设置exchange是为了满足类似社区等社交项目场景下，用户未登录的情况下可以查看某些接口的数据，
            // 登录情况下查看回显自己操作过的数据，和操作当前的数据
            // 如微博、抖音的内容数据，在未登录下可以查看数据，登录后可以点赞回复等，当前用户认证信息，设置以下信息即可，下游服务会解析
            return chain.filter(this.ignoreAuthenticationToken(authentication, exchange, request));
        }
        // 如果请求未携带token信息, 直接跳出
        if (StringUtils.isBlank(authentication) || !authentication.startsWith(BEARER)) {
            log.info("url:{},method:{},headers:{}, 请求未携带token信息", url, method, headers);
            return unauthorized(exchange);
        }
        //验证token合法交给下游服务验证用户权限
        if (!JwtAccessToken.invalidJwtAccessToken(authentication)) {
            ServerHttpRequest.Builder builder = request.mutate();
            //将jwt token中的用户信息传给服务
            builder.header(CustomHttpHeaders.X_CLIENT_TOKEN_USER, authentication);
            return chain.filter(exchange.mutate().request(builder.build()).build());
        }
        log.info("url:{},method:{},headers:{}, token验证失败", url, method, headers);
        return unauthorized(exchange);
    }

    /**
     * 网关拒绝，返回401
     *
     * @param serverWebExchange
     * @return Mono<Void>
     * @author 瑾年
     * @data 2019/06/18
     */
    private Mono<Void> unauthorized(ServerWebExchange serverWebExchange) {
        return WebFluxUtil.unauthorized(serverWebExchange);
    }

    /**
     * 只要带了token 校验合法就传给下游服务
     *
     * @param authentication
     * @param serverWebExchange
     * @param request
     * @return
     */
    private ServerWebExchange ignoreAuthenticationToken(String authentication, ServerWebExchange serverWebExchange, ServerHttpRequest request) {
        String url = request.getPath().value();
        if (!IGNORE.startsWith(url) && StringUtils.isNotBlank(authentication) && !JwtAccessToken.invalidJwtAccessToken(authentication)) {
            ServerHttpRequest.Builder builder = request.mutate();
            //将jwt token中的用户信息传给服务
            builder.header(CustomHttpHeaders.X_CLIENT_TOKEN_USER, authentication);
            return serverWebExchange.mutate().request(builder.build()).build();
        }
        return serverWebExchange;
    }

    @Override
    public int getOrder() {
        return 2;
    }

}
